﻿using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using UIOC.CodeAnalysis.Diagnostics;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Helpers;

namespace UIOC.CodeAnalysis.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class AutowiredAttributeImplementationAnalyzer : DiagnosticAnalyzer {

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(AutowiredAttributeImplementationAnalyzer)}] {message}");
    }

    private static readonly object ImplementInterfaceTypeSetLock = new();
    public static ImmutableHashSet<string> ImplementInterfaceTypeSet = ImmutableHashSet<string>.Empty;

    public static bool ContainsImplementInterface(ITypeSymbol interfaceSymbol) {
        var interfaceName = $"{interfaceSymbol.ContainingAssembly.Name} {interfaceSymbol.CreateSimpleNameSyntax(IsUnboundGenericType: true)}";
        return ImplementInterfaceTypeSet.Contains(interfaceName);
    }

    private static readonly object MetadataReferenceSetLock = new();
    public static ImmutableHashSet<string> MetadataReferenceSet = ImmutableHashSet<string>.Empty;

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        => ImmutableArray.Create(
            AutowiredAttributeMissingImplementationDiagnostic.Rule,
            AutowiredAttributeMustBeInterfaceDiagnostic.Rule
        );

    public override void Initialize(AnalysisContext context) {
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute);
    }

    private void AnalyzeAttribute(SyntaxNodeAnalysisContext context) {
        var compilation = context.Compilation;
        var assembly = compilation.Assembly;
        if (assembly.IsIgnore()) {
            return;
        }

        if (context.Node is not AttributeSyntax attributeSyntax) {
            return;
        }

        var implementAttributeSymbol = compilation.GetTypeByMetadataName(AttributeFullNames.Implement);
        if (implementAttributeSymbol == null) {
            return;
        }

        var autowiredAttributeSymbol = compilation.GetTypeByMetadataName(AttributeFullNames.Autowired);
        if (autowiredAttributeSymbol == null) {
            return;
        }

        var attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol;
        var isAutowiredAttribute = attributeSymbol?.ContainingType
            .Equals(autowiredAttributeSymbol, SymbolEqualityComparer.Default) == true;

        if (!isAutowiredAttribute) {
            return;
        }

        var fieldDeclaration = attributeSyntax.FirstAncestorOrSelf<FieldDeclarationSyntax>();
        if (fieldDeclaration == null) {
            return;
        }

        CollectAllImplementTypes(context, implementAttributeSymbol);

        foreach (var variable in fieldDeclaration.Declaration.Variables) {
            var fieldTypeSyntax = fieldDeclaration.Declaration.Type;
            var fieldTypeSymbol = context.SemanticModel.GetTypeInfo(fieldTypeSyntax).Type;
            if (fieldTypeSymbol == null) {
                continue;
            }
            if (fieldTypeSymbol.TypeKind == TypeKind.Interface) {
                if (!ContainsImplementInterface(fieldTypeSymbol)) {
                    context.ReportDiagnostic(Diagnostic.Create(
                        AutowiredAttributeMissingImplementationDiagnostic.Rule,
                        fieldTypeSyntax.GetLocation(),
                        variable.Identifier.Text,
                        fieldTypeSymbol.Name
                    ));
                }
            } else {
                context.ReportDiagnostic(Diagnostic.Create(
                    AutowiredAttributeMustBeInterfaceDiagnostic.Rule,
                    fieldDeclaration.Declaration.Type.GetLocation(),
                    variable.Identifier.Text,
                    fieldTypeSymbol.Name
                ));
            }
        }
    }

    public static void CollectAllImplementTypes(SyntaxNodeAnalysisContext context, INamedTypeSymbol implementAttributeSymbol) {

        foreach (var syntaxTree in context.Compilation.SyntaxTrees) {
            var root = syntaxTree.GetRoot();
#pragma warning disable IDE0079 // 请删除不必要的忽略
#pragma warning disable RS1030 // 请勿在诊断分析器中调用 Compilation.GetSemanticModel() 方法
            var semanticModel = context.Compilation.GetSemanticModel(syntaxTree);
#pragma warning restore RS1030 // 请勿在诊断分析器中调用 Compilation.GetSemanticModel() 方法
#pragma warning restore IDE0079 // 请删除不必要的忽略
            foreach (var classDeclarationSyntax in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) {
                var classSymbol = semanticModel.GetDeclaredSymbol(classDeclarationSyntax);
                if (classSymbol != null) {
                    var implementAttributeDatas = classSymbol.GetAttributes(implementAttributeSymbol);
                    foreach (var implementAttributeData in implementAttributeDatas) {
                        if (implementAttributeData.ConstructorArguments.Length > 0
                            && implementAttributeData.ConstructorArguments[0].Value is INamedTypeSymbol interfaceSymbol
                        ) {
                            var interfaceName = $"{interfaceSymbol.ContainingAssembly.Name} {interfaceSymbol.CreateSimpleNameSyntax(IsUnboundGenericType: true)}";
                            if (!ImplementInterfaceTypeSet.Contains(interfaceName)) {
                                lock (ImplementInterfaceTypeSetLock) {
                                    ImplementInterfaceTypeSet = ImplementInterfaceTypeSet.Add(interfaceName);
                                }
                            }
                        }
                    }
                }
            }
        }
        foreach (var reference in context.Compilation.References) {
            var metadataDisplay = reference.Display.ToString();
            if (MetadataReferenceSet.Contains(metadataDisplay)
                || context.Compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol assemblySymbol
                || assemblySymbol.IsIgnore()
            ) {
                continue;
            }
            foreach (var typeSymbol in GetAllTypes(assemblySymbol.GlobalNamespace).Where(symbol => symbol.TypeKind == TypeKind.Class)) {
                var implementAttributeDatas = typeSymbol.GetAttributes(implementAttributeSymbol);
                foreach (var implementAttributeData in implementAttributeDatas) {
                    if (implementAttributeData.ConstructorArguments.Length > 0
                        && implementAttributeData.ConstructorArguments[0].Value is INamedTypeSymbol interfaceSymbol
                    ) {
                        var interfaceName = $"{interfaceSymbol.ContainingAssembly.Name} {interfaceSymbol.CreateSimpleNameSyntax(IsUnboundGenericType: true)}";
                        if (!ImplementInterfaceTypeSet.Contains(interfaceName)) {
                            lock (ImplementInterfaceTypeSetLock) {
                                ImplementInterfaceTypeSet = ImplementInterfaceTypeSet.Add(interfaceName);
                            }
                        }
                    }
                }
            }
            if (!MetadataReferenceSet.Contains(metadataDisplay)) {
                lock (MetadataReferenceSetLock) {
                    MetadataReferenceSet = MetadataReferenceSet.Add(metadataDisplay);
                }
            }
        }
    }

    private static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol) {
        foreach (var member in namespaceSymbol.GetMembers()) {
            if (member is INamespaceSymbol ns) {
                foreach (var type in GetAllTypes(ns))
                    yield return type;
            } else if (member is INamedTypeSymbol type) {
                yield return type;
            }
        }
    }
    //private static IEnumerable<INamedTypeSymbol> GetTypesWithAttribute(
    //    Compilation compilation,
    //    INamedTypeSymbol attributeSymbol,
    //    INamedTypeSymbol interfaceSymbol
    //) {
    //    var allTrees = compilation.SyntaxTrees.ToList();
    //    foreach (var syntaxTree in allTrees) {
    //        var root = syntaxTree.GetRoot();
    //        var semanticModel = compilation.GetSemanticModel(syntaxTree);
    //        foreach (var classDeclaration in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) {
    //            var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
    //            if (classSymbol == null) {
    //                continue;
    //            }
    //            var attributeData = classSymbol.GetAttributes().FirstOrDefault(attr =>
    //                attr.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true
    //            );
    //            if (attributeData != null && attributeData.ConstructorArguments.Length > 0 && attributeData.ConstructorArguments[0].Value is INamedTypeSymbol argumentInterfaceSymbol && SymbolEqualityComparer.Default.Equals(argumentInterfaceSymbol, interfaceSymbol)) {
    //                LogInfo($"121212 {nameof(argumentInterfaceSymbol)}={argumentInterfaceSymbol.CreateSimpleNameSyntax()}, {nameof(interfaceSymbol)}={interfaceSymbol}");
    //                yield return classSymbol;
    //            }
    //        }
    //    }
    //    yield break;
    //}
}
